#ifndef __RenderSystemCapabilitiesSerializer_H__
#define __RenderSystemCapabilitiesSerializer_H__

#include "U2PreRequest.h"
#include "U2MemoryAllocatorConfig.h"
#include "U2RenderSystemCapabilities.h"
#include "U2DataStream.h"



U2EG_NAMESPACE_BEGIN


/** Class for serializing U2RenderSystemCapabilities to / from a .rendercaps script.*/
class _U2Share U2RenderSystemCapabilitiesSerializer : public RenderSysAlloc
{

public:
    /** default constructor*/
    U2RenderSystemCapabilitiesSerializer();
    /** default destructor*/
    virtual ~U2RenderSystemCapabilitiesSerializer() {}

    /** Writes a U2RenderSystemCapabilities object to a data stream */
    void writeScript(const U2RenderSystemCapabilities* caps, U2String name, U2String filename);
	
	/** Writes a U2RenderSystemCapabilities object to a string */
	U2String writeString(const U2RenderSystemCapabilities* caps, U2String name);

    /** Parses a U2RenderSystemCapabilities script file passed as a stream.
        Adds it to RenderSystemCapabilitiesManager::_addRenderSystemCapabilities
    */
    void parseScript(U2DataStreamPtr& stream);


protected:

    enum CapabilityKeywordType
    {
        UNDEFINED_CAPABILITY_TYPE = 0, 
        SET_STRING_METHOD, 
        SET_INT_METHOD, 
        SET_BOOL_METHOD, 
        SET_REAL_METHOD,
        SET_CAPABILITY_ENUM_BOOL, 
        ADD_SHADER_PROFILE_STRING
    };

    // determines what keyword is what type of capability. For example:
    // "automipmap" and "pbuffer" are both activated with setCapability (passing RSC_AUTOMIPMAP and RSC_PBUFFER respectivelly)
    // while "max_num_multi_render_targets" is an integer and has it's own method: setMaxMultiNumRenderTargets
    // we need to know these types to automatically parse each capability
    typedef std::map<U2String, CapabilityKeywordType> KeywordTypeMap;
    KeywordTypeMap mKeywordTypeMap;

    typedef void (U2RenderSystemCapabilities::*SetStringMethod)(const U2String&);
    // maps capability keywords to setCapability(U2String& cap) style methods
    typedef std::map<U2String, SetStringMethod> SetStringMethodDispatchTable;
    SetStringMethodDispatchTable mSetStringMethodDispatchTable;

    // SET_INT_METHOD parsing tables
    typedef void (U2RenderSystemCapabilities::*SetIntMethod)(u2ushort);
    typedef std::map<U2String, SetIntMethod> SetIntMethodDispatchTable;
    SetIntMethodDispatchTable mSetIntMethodDispatchTable;

    // SET_BOOL_METHOD parsing tables
    typedef void (U2RenderSystemCapabilities::*SetBoolMethod)(bool);
    typedef std::map<U2String, SetBoolMethod> SetBoolMethodDispatchTable;
    SetBoolMethodDispatchTable mSetBoolMethodDispatchTable;

    // SET_REAL_METHOD parsing tables
    typedef void (U2RenderSystemCapabilities::*SetRealMethod)(u2real);
    typedef std::map<U2String, SetRealMethod> SetRealMethodDispatchTable;
    SetRealMethodDispatchTable mSetRealMethodDispatchTable;

    typedef std::map<U2String, Capabilities> CapabilitiesMap;
    CapabilitiesMap mCapabilitiesMap;

    inline void addCapabilitiesMapping(U2String name, Capabilities cap)
    {
        mCapabilitiesMap.insert(CapabilitiesMap::value_type(name, cap));
    }


    // capabilities lines for parsing are collected along with their line numbers for debugging
    typedef std::vector<std::pair<U2String, int> > CapabilitiesLinesList;
    // the set of states that the parser can be in
    enum ParseAction {PARSE_HEADER, FIND_OPEN_BRACE, COLLECT_LINES};

    int mCurrentLineNumber;
    U2String* mCurrentLine;
    U2DataStreamPtr mCurrentStream;

    U2RenderSystemCapabilities* mCurrentCapabilities;

    inline void addKeywordType(U2String keyword, CapabilityKeywordType type)
    {
        mKeywordTypeMap.insert(KeywordTypeMap::value_type(keyword, type));
    }

    inline CapabilityKeywordType getKeywordType(const U2String& keyword) const
    {
		KeywordTypeMap::const_iterator it = mKeywordTypeMap.find(keyword);
        if(it != mKeywordTypeMap.end())
        {
		    return (*it).second;
        }
		else
		{
			 logParseError("Can't find the type for keyword: " + keyword);
			 return UNDEFINED_CAPABILITY_TYPE;
		}
    }

    inline void addSetStringMethod(U2String keyword, SetStringMethod method)
    {
        mSetStringMethodDispatchTable.insert(SetStringMethodDispatchTable::value_type(keyword, method));
    }

    inline void callSetStringMethod(U2String& keyword, U2String& val)
    {
        SetStringMethodDispatchTable::iterator methodIter = mSetStringMethodDispatchTable.find(keyword);
        if (methodIter != mSetStringMethodDispatchTable.end())
        {
			SetStringMethod m = (*methodIter).second;
            (mCurrentCapabilities->*m)(val);
        }
        else
        {
            logParseError("undefined keyword: " + keyword);
        }
    }


    inline void addSetIntMethod(U2String keyword, SetIntMethod method)
    {
        mSetIntMethodDispatchTable.insert(SetIntMethodDispatchTable::value_type(keyword, method));
    }

    inline void callSetIntMethod(U2String& keyword, u2ushort val)
    {
        SetIntMethodDispatchTable::iterator methodIter = mSetIntMethodDispatchTable.find(keyword);
        if (methodIter != mSetIntMethodDispatchTable.end())
        {
			SetIntMethod m = (*methodIter).second;
            (mCurrentCapabilities->*m)(val);
        }
        else
        {
            logParseError("undefined keyword: " + keyword);
        }  
    }


    inline void addSetBoolMethod(U2String keyword, SetBoolMethod method)
    {
        mSetBoolMethodDispatchTable.insert(SetBoolMethodDispatchTable::value_type(keyword, method));
    }

    inline void callSetBoolMethod(U2String& keyword, bool val)
    {
        SetBoolMethodDispatchTable::iterator methodIter = mSetBoolMethodDispatchTable.find(keyword);
        if (methodIter != mSetBoolMethodDispatchTable.end())
        {
			SetBoolMethod m = (*methodIter).second;
            (mCurrentCapabilities->*m)(val);
        }
        else
        {
            logParseError("undefined keyword: " + keyword);
		}
    }


    inline void addSetRealMethod(U2String keyword, SetRealMethod method)
    {
        mSetRealMethodDispatchTable.insert(SetRealMethodDispatchTable::value_type(keyword, method));
    }

    inline void callSetRealMethod(U2String& keyword, u2real val)
    {
        SetRealMethodDispatchTable::iterator methodIter = mSetRealMethodDispatchTable.find(keyword);
        if (methodIter != mSetRealMethodDispatchTable.end())
        {
					    SetRealMethod m = (*methodIter).second;
            (mCurrentCapabilities->*m)(val);
        }
        else
        {
            logParseError("undefined keyword: " + keyword);
					}
    }

    inline void addShaderProfile(U2String& val)
    {
        mCurrentCapabilities->addShaderProfile(val);
    }

    inline void setCapabilityEnumBool(U2String& name, bool val)
    {
        // check for errors
        if(mCapabilitiesMap.find(name) == mCapabilitiesMap.end())
        {
            logParseError("Undefined capability: " + name);
            return;
        }
        // only set true capabilities, we can't unset false
        if(val)
        {
            Capabilities cap = mCapabilitiesMap[name];
            mCurrentCapabilities->setCapability(cap);
        }
    }

    void initialiaseDispatchTables();

    void parseCapabilitiesLines(CapabilitiesLinesList& linesList);

    void logParseError(const U2String& error) const;

};


U2EG_NAMESPACE_END


#endif
